/**
 * @file thread_pool.hpp
 * @date 2021/10/29 10:10
 *
 * @author harrybao (harrybao@123u.com)
 *
 * @brief 线程池
 *
 * @version v1.0.0
 *
 * @copyright Copyright © 2021 harrybao All Rights Reserved
 *
 * @par history:
 * <table>
 * <tr><th>Date                     <th>Version     <th>Author <th>Description
 * <tr><td>2021/10/29 10:10         <td>1.0.0       <td>harrybao
 * <td>创建初始版本
 * </table>
 */

#pragma once

#include <atomic>
#include <condition_variable>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include <queue>

namespace hthread {

template <typename T, std::size_t N = 16>
class thread_pool {
 public:
  using func_type = T;
  using queue_type = std::queue<std::function<void()>>;

 private:
  class thread_worker {
   private:
    int m_id;
    unsigned int m_task_id;
    thread_pool& m_pool;

   public:
    thread_worker(thread_pool* pool, int id)
        : m_pool(*pool), m_id(id), m_task_id(0) {
      std::cout << "[" << m_id << "] thread_worker" << '\n';
    }

    void operator()() {
      func_type func;
      while (!m_pool.m_shutdown) {
        std::unique_lock<std::mutex> lock(m_pool.m_mtx);
        m_pool.m_cond.wait(lock, [this]() -> bool {
          return !m_pool.m_queue.empty() || m_pool.m_shutdown;
        });

        if (m_pool.m_queue.empty() || m_pool.m_shutdown) {
          break;
        }

        func = std::move(m_pool.m_queue.front());
        m_pool.m_queue.pop();

        m_task_id++;

        func();
      }
      {
        std::cout << "[" << m_id << "] "
                  << "tid:" << std::this_thread::get_id()
                  << " task_id:" << m_task_id << '\n';
      }
    }
  };

 public:
  thread_pool() : thread_pool(N) {}

  thread_pool(const thread_pool&) = delete;
  thread_pool(thread_pool&&) = delete;

  thread_pool& operator=(const thread_pool&) = delete;
  thread_pool& operator=(thread_pool&&) = delete;

  ~thread_pool() { shutdown(); }

 private:
  thread_pool(std::size_t n) : m_threads(n), m_shutdown(false) {}

 public:
  void init() {
    for (std::size_t i = 0; i < m_threads.size(); ++i) {
      m_threads[i] = std::thread(thread_worker(this, i));
    }
  }

  void shutdown() {
    if (m_shutdown) {
      return;
    }

    m_shutdown = true;
    m_cond.notify_all();

    for (auto& t : m_threads) {
      if (t.joinable()) {
        t.join();
      }
    }
  }

  template <typename F, typename... Args>
  auto submit(F&& f, Args... args) -> std::future<decltype(f(args...))> {
    using return_type = decltype(f(args...))();

    auto task_ptr = std::make_shared<std::packaged_task<return_type>>(
        std::bind(std::forward<F>(f), std::forward<Args>(args)...));

    {
      std::unique_lock<std::mutex> lock(m_mtx);
      m_queue.emplace([task_ptr]() { (*task_ptr)(); });
    }

    m_cond.notify_one();

    return task_ptr->get_future();
  }

 private:
  std::mutex m_mtx;
  std::vector<std::thread> m_threads;
  queue_type m_queue;
  std::atomic<bool> m_shutdown;
  std::condition_variable m_cond;
};

}  // namespace hthread